﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;
using MacomberMapSystem.Common.Database;
using MacomberMapSystem.Common.Serialization;
using System.IO;
using MacomberMapSystem.Common.Integration.Blackstart;
using System.Xml;
using System.Threading;
using MacomberMapSystem.Common.CIM.Direct;

namespace MacomberMapSystem.Common.User_Interfaces.Database
{
    /// <summary>
    /// This class offers a UI for editing synchroscope information
    /// </summary>
    public partial class frmSynchroscopeEditor : DockContent
    {
        #region Variable declarations
        /// <summary>Our system repository</summary>
        public MM_Repository Repository;

        /// <summary>Our database connection</summary>
        public MM_Database_Connector Db;

        /// <summary>Our collection of models</summary>
        public Dictionary<int, MM_Database_Model> Models;

        /// <summary>The currently-selected model</summary>
        public MM_Database_Model Model = null;

        /// <summary>Our audit log</summary>
        public StringBuilder AuditLog = new StringBuilder();

        /// <summary>Our collection of synchroscopes</summary>
        public List<MM_Synchroscope> Synchroscopes = new List<MM_Synchroscope>();
        
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize our synchroscope editor
        /// </summary>
        /// <param name="Repository"></param>
        /// <param name="Models"></param>
        /// <param name="Db"></param>
        public frmSynchroscopeEditor(MM_Repository Repository, Dictionary<int, MM_Database_Model> Models, MM_Database_Connector Db)
        {
            InitializeComponent();
            this.Db = Db;
            this.Repository = Repository;
            this.Models = Models;
            foreach (MM_Database_Model Mdl in Models.Values)
                if (!cmbModelCategory.Items.Contains(Mdl.ModelCategory))
                    cmbModelCategory.Items.Add(Mdl.ModelCategory);
           
            OneLinePreview.EnableDragDrop = true;      
        }
        #endregion

        #region Model selection
        /// <summary>
        /// Update our current selection of models
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cmbModelCategory_SelectedIndexChanged(object sender, EventArgs e)
        {

            cmbModel.Items.Clear();
            SortedDictionary<int, MM_Database_Model> SortedModels = new SortedDictionary<int, MM_Database_Model>();
            foreach (MM_Database_Model Model in this.Models.Values)
                if (Model.ModelCategory == cmbModelCategory.Text)
                    SortedModels.Add(-Model.ID, Model);
            cmbModel.Items.AddRange(SortedModels.Values.ToArray());
        }

        /// <summary>
        /// Refresh our collection of models
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRefresh_Click(object sender, EventArgs e)
        {
            cmbModel.SelectedIndex = -1;
            cmbModel.Items.Clear();
            new MM_Login_Process("Loading models", Repository.LoadModels).Invoke();                        
            foreach (MM_Database_Model Mdl in Models.Values)
                if (!cmbModelCategory.Items.Contains(Mdl.ModelCategory))
                    cmbModelCategory.Items.Add(Mdl.ModelCategory);
        }


        /// <summary>
        /// Select a model by date
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSelect_Click(object sender, EventArgs e)
        {
            using (frmSelectModel SelModel = new frmSelectModel(Repository))
                if (SelModel.ShowDialog() == DialogResult.OK)
                {
                    cmbModelCategory.SelectedItem = SelModel.SelectedModel.ModelCategory;
                    cmbModel.SelectedItem = SelModel.SelectedModel;
                }
        }

        /// <summary>
        /// When the user selects a model, make sure it's opened.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cmbModel_SelectedIndexChanged(object sender, EventArgs e)
        {

            if (cmbModel.SelectedItem != null)
            {
                MM_Database_Model Model = cmbModel.SelectedItem as MM_Database_Model;
                if (Model is CIM_DirectModel == false)
                {
                    String IndexPath = Path.Combine(Model.FullPath, Model.Name + ".CimIndex");
                    if (!File.Exists(IndexPath))
                        MessageBox.Show("Unable to locate the model's Cim Index!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Stop);
                    else
                        Model = CIM_DirectModel.OpenModel(Model, Repository, Models, cmbModel);                                       
                }
                if (Model.EnsureOpened())
                {
                    this.Model = Model;
                    Synchroscopes.Clear();
                    int MatchCount = 0;
                    using (System.Data.Common.DbCommand dCmd = Repository.Db.CreateCommand("SELECT * FROM MM_SYNCHROSCOPE ORDER BY SUBSTATION,ELEMTYPE,NAME"))
                    using (System.Data.Common.DbDataReader dRd = dCmd.ExecuteReader())
                        while (dRd.Read())
                        {
                            MM_Synchroscope NewScope = new MM_Synchroscope(dRd, Repository);
                            NewScope.RdfID = null;
                            NewScope.ElemType = "";

                            //First, find our substation
                            MacomberMapSystem.Common.CIM.CIM_Element Substation;
                            if (NewScope.Substation.StartsWith("*"))
                                Model.Companies.TryGetValue(NewScope.Substation.Substring(1), out Substation);
                            else
                                Model.Substations.TryGetValue(NewScope.Substation, out Substation);

                            if (Substation == null)
                                NewScope.Matched = false;
                            else if (String.IsNullOrEmpty(NewScope.Name))
                            {
                                NewScope.ElemType = Substation.ElemType;
                                NewScope.RdfID = Substation.rdfID;
                                NewScope.Matched = true;
                            }
                            else
                                foreach (MacomberMapSystem.Common.CIM.CIM_Element Elem in Substation.Elements)
                                    if (NewScope.RdfID == null && (Elem.TypeName == "cim:Breaker" || Elem.TypeName == "cim:Disconnector") && String.Equals(Elem.Name, NewScope.Name, StringComparison.CurrentCultureIgnoreCase))
                                    {
                                        NewScope.RdfID = Elem.rdfID;
                                        NewScope.ElemType = Elem.ElemType;
                                        NewScope.Matched = true;                                        
                                    }
                            Synchroscopes.Add(NewScope);
                            if (NewScope.Matched)
                                MatchCount++;
                            Application.DoEvents();
                        }
                    UpdateSynchroscopes();
                    MessageBox.Show("Out of " + Synchroscopes.Count.ToString("#,##0") + " synchroscopes, " + ((MatchCount * 100) / Synchroscopes.Count).ToString() + "% (" + MatchCount.ToString("#,##0") + ") were matched.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);                
                }
            }
        }
        #endregion

        #region User interactions
        /// <summary>
        /// Allow the importing of corridor data
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnImport_Click(object sender, EventArgs e)
        {           
            DialogResult Resp = MessageBox.Show("Would you like to load the synchroscope from the clipboard (no opens from a file)?", Application.ProductName, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
            String StringToProcess;
            if (Resp == DialogResult.Cancel)
                return;
            else if (Resp == DialogResult.Yes)
                StringToProcess = Clipboard.GetText(TextDataFormat.Text);
            else
                using (OpenFileDialog oFd = new OpenFileDialog())
                {
                    oFd.Title = "Select synchroscope information";
                    oFd.Filter = "Synchroscope data (*.txt)|*.txt";
                    if (oFd.ShowDialog() != DialogResult.OK)
                        return;
                    using (StreamReader sRd = new StreamReader(oFd.FileName))
                        StringToProcess = sRd.ReadToEnd();
                }

            int MatchCount = 0;
            using (StringReader sRd = new StringReader(StringToProcess))
            {                            
                String InLine;
                Synchroscopes.Clear();
                lvItems.Items.Clear();
                    while ((InLine = sRd.ReadLine()) != null)
                        if (InLine != "Substation,Type,Name,rdfID")
                        {
                            MM_Synchroscope NewScope = new MM_Synchroscope(InLine, Model);
                            Synchroscopes.Add(NewScope);
                            if (NewScope.Matched)
                                MatchCount++;
                            if (Synchroscopes.Count % 25 == 0)
                                Application.DoEvents();
                        }

                UpdateSynchroscopes();
            }
            MessageBox.Show("Out of " + Synchroscopes.Count.ToString("#,##0") + " synchroscopes, " + ((MatchCount * 100) / Synchroscopes.Count).ToString() + "% (" + MatchCount.ToString("#,##0") + ") were matched.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);                

        }

        /// <summary>
        /// Update our synchroscope collection
        /// </summary>
        private void UpdateSynchroscopes()
        {
            lvItems.Items.Clear();                                 
            for (int a = 0; a < Synchroscopes.Count; a++)
            {
                ListViewItem lvI = new ListViewItem((a+1).ToString());
                MM_Synchroscope NewScope = Synchroscopes[a];
                lvI.SubItems.Add(MacomberMapSystem.Common.Internals.MM_Type_Converter.TitleCase(NewScope.Substation));
                lvI.SubItems.Add(NewScope.ElemType);
                lvI.SubItems.Add(MacomberMapSystem.Common.Internals.MM_Type_Converter.TitleCase(NewScope.Name));
                lvI.Tag = NewScope;
                if (NewScope.RdfID == null)
                {
                    lvI.ForeColor = Color.Red;
                    lvI.UseItemStyleForSubItems = true;
                }
                lvItems.Items.Add(lvI);    
            }
            lvItems.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
        }
        #endregion

        /// <summary>
        /// Hanlde the drag/drop of an item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvItems_DragDrop(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(typeof(XmlElement)))
                return;
            XmlElement AssociatedElement = e.Data.GetData(typeof(XmlElement)) as XmlElement;
            ListViewHitTestInfo hti = lvItems.HitTest(lvItems.PointToClient(new Point(e.X, e.Y)));

            if (hti.Item != null)
            {
                MM_Synchroscope Scope = hti.Item.Tag as MM_Synchroscope;


                //Find the current substation we're in.
                MacomberMapSystem.Common.CIM.CIM_Element NewSub = null;
                if (AssociatedElement.OwnerDocument.DocumentElement.HasAttribute("rdfID") && !String.IsNullOrEmpty(AssociatedElement.OwnerDocument.DocumentElement.Attributes["rdfID"].Value))
                    NewSub = Model.FromRdfID(AssociatedElement.OwnerDocument.DocumentElement.Attributes["rdfID"].Value);
                else
                    NewSub = Model.FromTEID(Convert.ToUInt64(AssociatedElement.OwnerDocument.DocumentElement.Attributes["BaseElement.TEID"].Value));
                MacomberMapSystem.Common.CIM.CIM_Element NewElem = Model.FromRdfID(AssociatedElement.Attributes["rdfID"].Value);

                AuditLog.AppendLine(String.Format("{0}.{1} was replaced with {2}.{3}", Scope.Substation, Scope.Name, NewSub.Name, NewElem.ToString()));                               
                Scope.Substation = hti.Item.SubItems[1].Text = NewSub.Name;
                Scope.ElemType = hti.Item.SubItems[2].Text = NewElem.ElemType;
                Scope.Name = hti.Item.SubItems[3].Text = NewElem.Name;
                hti.Item.ForeColor = lvItems.ForeColor;
            }
            lvItems.InsertionMark.Index = -1;
        }

        /// <summary>
        /// Handle the enter of dragged item into the listview.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvItems_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(typeof(XmlElement)))
                e.Effect = DragDropEffects.All;
        }

        /// <summary>
        /// When we drag/move, highlight our target element
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvItems_DragOver(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(typeof(XmlElement)))
            {
                Point ItemPoint = lvItems.PointToClient(new Point(e.X, e.Y));
                int targetIndex = lvItems.InsertionMark.NearestIndex(ItemPoint);
                if (targetIndex > -1)
                {
                    Rectangle itemBounds = lvItems.GetItemRect(targetIndex);
                    if (ItemPoint.X > itemBounds.Left + (itemBounds.Width / 2))
                        lvItems.InsertionMark.AppearsAfterItem = true;
                    else
                        lvItems.InsertionMark.AppearsAfterItem = false;
                }
                lvItems.InsertionMark.Index = targetIndex;
            }
        }

        /// <summary>
        /// When the drag stops, hide our insertion mark.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvItems_DragLeave(object sender, EventArgs e)
        {
            lvItems.InsertionMark.Index = -1;
        }

        /// <summary>
        /// Handle a mouse click event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvItems_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Right)
                return;
            ListViewHitTestInfo hti = lvItems.HitTest(e.Location);
            cmsItems.Items.Clear();

            if (hti.Item != null)
            {
                MM_Synchroscope Synch = hti.Item.Tag as MM_Synchroscope;
                if (!String.IsNullOrEmpty(Synch.Substation))
                    cmsItems.Items.Add("Substation: " + Synch.Substation);
                if (Synch.RdfID == null)
                    if (Synch.Name == null)                
                    {}
                    else                
                    cmsItems.Items.Add("Element: " + Synch.Name);
                else
                    Model.FromRdfID(Synch.RdfID).WriteMenuElement(cmsItems.Items, cmsItems);
                cmsItems.Items.Add("-");
                cmsItems.Items.Add("Insert &above");
                cmsItems.Items.Add("Insert &below");
                cmsItems.Items.Add("&Edit");
                cmsItems.Items.Add("&Delete");
                cmsItems.Tag = hti.Item;
            }
            else
                cmsItems.Items.Add("&Add new element");
            cmsItems.Show((sender as Control), e.Location);
        }

        private void lvItems_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right && lvItems.HitTest(e.Location).Item == null)
            {
                cmsItems.Items.Clear();
                cmsItems.Items.Add("&Add new element");
                cmsItems.Show((sender as Control), e.Location);
            }
        }

        private void lvItems_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lvItems.SelectedItems.Count != 1)
                return;// OneLinePreview.AssignOneLine(null, null, null, Repository);
            MM_Synchroscope Sync = lvItems.SelectedItems[0].Tag as MM_Synchroscope;
            MacomberMapSystem.Common.CIM.CIM_Element Sub;
            
            if (Model.Substations.TryGetValue(Sync.Substation, out Sub))
            if (OneLinePreview.ActiveOneLine == null || !OneLinePreview.ActiveOneLine.rdfID.Equals(Sub.rdfID))
                using (System.Data.Common.DbCommand dCmd = Db.CreateCommand("SELECT ID FROM MM_DATABASE_ONELINE WHERE RDFID=:0 AND ONELINETYPE=0 AND MODELS LIKE :1", Sub.rdfID.ToString(), "%," + Model.ID.ToString() + ",%"))
                using (System.Data.Common.DbDataReader dRd = dCmd.ExecuteReader())
                    if (dRd.Read())
                    {
                        Bitmap OLImg = Db.LoadOneLineImage(Convert.ToInt32(dRd["ID"]));
                        MM_Database_OneLine OneLine = Db.LoadOneLine(Convert.ToInt32(dRd["ID"]), Repository);
                        OneLinePreview.AssignOneLine(OneLine, OLImg, Model, Repository);
                    }

            TreeView tvOneLine = (TreeView)OneLinePreview.GetType().GetField("tvOneLine", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(OneLinePreview);
            TreeNode FoundNode;
            if (Sync.RdfID == null)
                tvOneLine.SelectedNode = null;
            else if (OneLinePreview.Elements.TryGetValue(Sync.RdfID, out FoundNode))
            {
                tvOneLine.SelectedNode = FoundNode;
                Panel pnlPicture = (Panel)OneLinePreview.GetType().GetField("pnlPicture", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(OneLinePreview);
                pnlPicture.AutoScrollPosition = ((KeyValuePair<XmlElement, Rectangle>)FoundNode.Tag).Value.Location;
            }
        }


        /// <summary>
        /// Save our synchroscope data
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSave_Click(object sender, EventArgs e)
        {
            //Update all synchroscopes
            foreach (MM_Synchroscope Sync in Synchroscopes)
                if (Sync.ElemType != "Switch")
                    if (Sync.ID == int.MinValue)
                        Sync.WriteRdb(Repository);
                    else
                        Sync.UpdateRdb(Repository);
        }

        /// <summary>
        /// Write out our list of changes
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnExportChanges_Click(object sender, EventArgs e)
        {
            using (SaveFileDialog sFd = new SaveFileDialog())
            {
                sFd.Title = "Select a file name for writing out the synchroscope audit log";
                sFd.Filter = "Text file (*.txt)|*.txt";
                sFd.FileName = "Syncroscope_Audit_" + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss") + ".txt";
                if (sFd.ShowDialog() == DialogResult.OK)
                    using (StreamWriter sW = new StreamWriter(sFd.FileName))
                        sW.WriteLine(AuditLog.ToString());
                
                sFd.Title = "Select a file name for writing out the synchroscope full file";
                sFd.Filter = "Comma-delimited file (*.csv)|*.csv";
                sFd.FileName = "Syncroscope_FullList_" + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss") + ".csv";
                if (sFd.ShowDialog() == DialogResult.OK)
                    using (StreamWriter sW = new StreamWriter(sFd.FileName))
                    {
                        sW.WriteLine("Substation,Type,Name,rdfID");
                        foreach (MM_Synchroscope Scope in Synchroscopes)
                            sW.WriteLine("{0},{1},{2},{3}", Scope.Substation, Scope.ElemType, (Scope.Name ?? ""), Scope.RdfID == null ? "" : Scope.RdfID.ToString());
                    }

                sFd.Title = "Select a file name for writing out the synchroscope unmatched file";
                sFd.Filter = "Comma-delimited file (*.csv)|*.csv";
                sFd.FileName = "Syncroscope_Unmatched_" + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss") + ".csv";
                if (sFd.ShowDialog() == DialogResult.OK)
                    using (StreamWriter sW = new StreamWriter(sFd.FileName))
                    {
                        sW.WriteLine("Substation,Type,Name,rdfID");
                        foreach (MM_Synchroscope Scope in Synchroscopes)
                            if (Scope.RdfID == null)
                                sW.WriteLine("{0},{1},{2},{3}", Scope.Substation, Scope.ElemType, (Scope.Name ?? ""), Scope.RdfID == null ? "" : Scope.RdfID.ToString());
                    }
            }
        }
        /// <summary>
        /// Handle the clicking of a menu item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cmsItems_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            
            MM_Synchroscope SelectedSynch = null;
            if (cmsItems.Tag is ListViewItem)
                SelectedSynch = (cmsItems.Tag as ListViewItem).Tag as MM_Synchroscope;
            if (e.ClickedItem.Text == "&Add new element" || e.ClickedItem.Text == "Insert &below" || e.ClickedItem.Text == "&Edit" || e.ClickedItem.Text == "Insert &above")
                using (frmBlackstartElement NewBE = new frmBlackstartElement(Model, SelectedSynch))
                    if (NewBE.ShowDialog(this) == DialogResult.OK)
                    {
                        ListViewItem lvI = cmsItems.Tag as ListViewItem;
                        MM_Synchroscope Synchroscope=null;                        
                        if (lvI != null)
                            Synchroscope = lvI.Tag as MM_Synchroscope;
                        if (e.ClickedItem.Text == "&Edit")
                        {
                            AuditLog.AppendLine(String.Format("{0}.{1} was changed to {2}.{3}.", Synchroscope.Substation, Synchroscope.Name, NewBE.Substation.Name, NewBE.SelectedElement));
                            Synchroscope.Substation = NewBE.Substation.Name;
                            Synchroscope.RdfID = NewBE.SelectedElement.rdfID;
                            Synchroscope.ElemType = NewBE.SelectedElement.ElemType;
                            Synchroscope.Name= NewBE.SelectedElement.Name;                            
                        }
                        else if (e.ClickedItem.Text == "Insert &below")
                        {
                            AuditLog.AppendLine(String.Format("{0}.{1} was inserted to #{2}.", NewBE.Substation.Name, NewBE.SelectedElement, lvI.Index + 2));
                            Synchroscopes.Insert(lvI.Index + 1, new MM_Synchroscope(NewBE.Substation, NewBE.SelectedElement));
                        }
                        else if (e.ClickedItem.Text == "Insert &above")
                        {
                            AuditLog.AppendLine(String.Format("{0}.{1} was inserted to #{2}.", NewBE.Substation.Name, NewBE.SelectedElement, lvI.Index + 1));
                            Synchroscopes.Insert(lvI.Index, new MM_Synchroscope(NewBE.Substation, NewBE.SelectedElement));
                        }
                        else if (e.ClickedItem.Text == "&Add new element")
                        {
                            AuditLog.AppendLine(String.Format("{0}.{1} was inserted to #{2}.", NewBE.Substation.Name, NewBE.SelectedElement, Synchroscopes.Count + 1));
                            Synchroscopes.Add( new MM_Synchroscope(NewBE.Substation, NewBE.SelectedElement));
                        }                    
                    }
            UpdateSynchroscopes();
        }
    }
}